package com.medisign.sign.log.job;

import com.medisign.sign.log.config.SystemConfig;
import com.medisign.sign.log.dao.TbSignDataDao;
import com.medisign.sign.log.dao.TbSignLogDao;
import com.medisign.sign.log.pojo.QTbSignFile;
import com.medisign.sign.log.pojo.TbSignData;
import com.medisign.sign.log.pojo.TbSignFile;
import com.medisign.sign.log.pojo.TbSignLog;
import com.medisign.sign.log.service.IRepairService;
import com.medisign.sign.log.service.ISignService;
import com.medisign.sign.log.service.impl.RepairServiceImpl;
import com.medisign.sign.log.util.Base64Util;
import com.medisign.sign.log.util.BaseDataSourceUtil;
import com.medisign.sign.log.util.DateUtil;
import com.medisign.sign.log.util.RedisUtil;
import com.medisign.sign.log.util.Sm3Util;
import com.medisign.sign.log.vo.P1SignRespVo;
import com.medisign.sign.log.vo.SignLogFileInfo;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;


/**
 * @author ljt
 * @date 2025/4/1
 */
@RequiredArgsConstructor
@Component
public class TaskScheduled {
    private static final Logger log = LoggerFactory.getLogger(TaskScheduled.class);

    private final SystemConfig systemConfig;

    private final RedisUtil redisUtil;

    private final ISignService signService;

    private final TbSignDataDao signDataDaoDao;

    private final TbSignLogDao signLogDao;

    private final JPAQueryFactory jpaQueryFactory;

    private final BaseDataSourceUtil dataSourceUtil;

    private final IRepairService repairService;

    private static final String P1_CONCAT_TEMPLATE = "%s@%s";

    private static final String LOG_TEMPLATE = "[%s], fileCode:[%s], sm3:[%s]";

    private int currentFileRaw;

    private Date currentFileStartTime;

    private Date currentFileEndTime;

    private int writeTotalCount;

    private int totalCount;

    private static volatile boolean isRunning = true;

    Date lastDate;

    SimpleDateFormat sdf;

    @Scheduled(cron = "* 0/10 * * * *")
    public void run() {
        if (!this.redisUtil.tryLock("doctor_sign_file_log_lock", 24, TimeUnit.HOURS)) {
            log.info("获取锁失败,已有线程正在处理...");
            return;
        }
        try {
            log.info("定时任务开始执行");
            String baseSignLogPath = systemConfig.getPath().getSignLogPath();
            isRunning = true;
            String idStr = this.redisUtil.getString("doctor_sign_file_log_id");
            Long queryId = StringUtils.isNotBlank(idStr) ? Long.parseLong(idStr) : 0L;
            log.info("开始执行日志保存!queryId:[{}]", queryId);
            String logFileName = getSignLogFileName();
            if (StringUtils.isNotBlank(logFileName)) {
                SignLogFileInfo signLogFileInfo = RepairServiceImpl.getFileDate(baseSignLogPath + logFileName);
                if (null != signLogFileInfo) {
                    this.currentFileRaw = signLogFileInfo.getRawCount();
                    this.currentFileStartTime = signLogFileInfo.getStartTime();
                    this.currentFileEndTime = signLogFileInfo.getEndTime();
                }
            }
            String strategy = null;
            int page = 0;
            while (true) {
                List<TbSignFile> signFileList = getSignFileList(queryId, page);
                if (CollectionUtils.isEmpty(signFileList)) {
                    log.info("查询集合为空!完成签署日志写入");
                    break;
                }
                Optional<TbSignFile> maxIdOptional = signFileList.stream().max(Comparator.comparing(TbSignFile::getId));
                signFileList = signFileList.stream().filter(s -> ("1".equals(s.getStatus()) && "1".equals(s.getIsValid()))).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(signFileList)) {
                    if (maxIdOptional.isPresent()) {
                        queryId = maxIdOptional.get().getId();
                        log.info("过滤可用数据为空,当前最大id为:[{}]", queryId);
                        this.redisUtil.set("doctor_sign_file_log_id", String.valueOf(queryId));
                    }
                    continue;
                }
                long start = System.currentTimeMillis();
                for (TbSignFile signFile : signFileList) {
                    if (!isRunning) {
                        log.info("当前文件名称[{}], 当前查询id:[{}], 当前fileCode:[{}],已写入总数:[{}]",  getCurrentLogFileName(), signFile.getId(), signFile.getFileCode(), this.writeTotalCount);
                        return;
                    }
                    this.totalCount++;
                    if (StringUtils.isBlank(getSignLogFileName())) {
                        genSignLogFileName(signFile.getCreateTime());
                    }
                    String sm3 = genSm3(signFile);
                    if (StringUtils.isBlank(sm3)){
                        continue;
                    }
                    strategy = signFile.getStrategy();
                    String dateStr = null;
                    if (Objects.nonNull(signFile.getSignDate())) {
                        dateStr = sdf.format(signFile.getSignDate());
                    } else if (Objects.nonNull(signFile.getCreateTime())) {
                        dateStr = sdf.format(signFile.getCreateTime());
                    }
                    Date signDate = Objects.nonNull(signFile.getSignDate()) ? signFile.getSignDate() : signFile.getCreateTime();
                    this.lastDate = signDate;
                    if (Objects.isNull(this.currentFileStartTime)) {
                        this.currentFileStartTime = signDate;
                    } else if (signDate.getTime() < this.currentFileStartTime.getTime()) {
                        this.currentFileStartTime = signDate;
                    }
                    if (Objects.isNull(this.currentFileEndTime)) {
                        this.currentFileEndTime = signDate;
                    } else if (this.currentFileEndTime.getTime() < signDate.getTime()) {
                        this.currentFileEndTime = signDate;
                    }
                    this.writeTotalCount++;
                    String rawContent = String.format("[%s], fileCode:[%s], sm3:[%s]", dateStr, signFile.getFileCode(), sm3);
                    this.redisUtil.setRightList("doctor_sign_file_log_list", rawContent);
                    queryId = signFile.getId();
                    this.redisUtil.set("doctor_sign_file_log_id", String.valueOf(queryId));
                    writeRedisToFile(queryId);
                    signAndSaveLogFile(queryId, strategy, signFile);
                }
                log.info("执行条数:[{}],耗时:[{}]ms", signFileList.size(), System.currentTimeMillis() - start);
            }
            Long redisListSize = getRedisListSize();
            if (redisListSize <= 0L) {
                log.info("redisListSize集合为空,定时任务执行完成...当前文件名称:[{}], 总数:[{}],写入总数:[{}]", getCurrentLogFileName(), this.totalCount, this.writeTotalCount);
                return;
            }
            log.info("定时任务执行完成...当前文件名称:[{}], 总数:[{}],写入总数:[{}]", getCurrentLogFileName(), this.totalCount, this.writeTotalCount);
        } finally {
            this.redisUtil.deleteByKey("doctor_sign_file_log_lock");
        }
    }

    private void writeRedisToFile(Long queryId) {
        Long redisListSize = getRedisListSize();
        if (redisListSize >= this.systemConfig.getSign().getRedisSizeLimit()) {
            this.currentFileRaw = (int)(this.currentFileRaw + redisListSize);
            String writeContent = getRedisContent();
            writeToFile(writeContent);
            log.debug("将redis内容写入文件,当前文件名称:[{}],当前查询id:[{}]", getCurrentLogFileName(), queryId);
        }
    }

    private void signAndSaveLogFile(Long queryId, String strategy, TbSignFile signFile) {
        if (this.currentFileRaw >= this.systemConfig.getSign().getFileSizeLimit()) {
            log.info("保存文件并签名,当前文件名称:[{}],当前查询id:[{}]", getCurrentLogFileName(), queryId);
            signAndSaveLogFile(strategy);
            genSignLogFileName(signFile.getCreateTime());
        }
    }

    private void signAndSaveLogFile(String strategy) {
        String fullSignLogPath = getFullSignLogPath();
        String logFileBase64 = Base64Util.fileToBase64(fullSignLogPath);
        String logFileSm3 = sm3(logFileBase64);
        P1SignRespVo p1SignRespVo = this.signService.p1Sign(logFileSm3);
        saveSignLog(logFileSm3, p1SignRespVo, strategy);
    }

    private void saveSignLog(String logFileSm3, P1SignRespVo p1SignRespVo, String strategy) {
        TbSignLog signLog = new TbSignLog();
        String fullSignLogPath = getFullSignLogPath();
        SignLogFileInfo signLogFileInfo = RepairServiceImpl.getFileDate(fullSignLogPath);
        if (null != signLogFileInfo) {
            signLog.setStartTime(signLogFileInfo.getStartTime());
            signLog.setEndTime(signLogFileInfo.getEndTime());
        } else {
            signLog.setStartTime(this.currentFileStartTime);
            signLog.setEndTime(this.currentFileEndTime);
        }
        signLog.setFileCode(p1SignRespVo.getFileCode());
        signLog.setLogPath(getSignLogFileName());
        signLog.setFileCode(p1SignRespVo.getFileCode());
        signLog.setUnsignData(logFileSm3);
        signLog.setSignedData(p1SignRespVo.getSignedData());
        signLog.setSignCert(p1SignRespVo.getSignCert());
        signLog.setTimestamp(p1SignRespVo.getTimestamp());
        signLog.setStrategy(strategy);
        this.dataSourceUtil.save(signLog);
    }

    public String getCurrentLogFileName() {
        return this.redisUtil.getString("doctor_sign_file_log_current_name");
    }

    public String setCurrentLogFileName(String fileName) {
        String preFileName = getCurrentLogFileName();
        log.info("setCurrentLogFileName 上一次保存的日志文件名:[{}]", preFileName);
        redisUtil.set("doctor_sign_file_log_current_name", fileName);
        return preFileName;
    }

    public void stop() {
        isRunning = false;
    }

    private String getRedisContent() {
        List<String> sm3List = (List<String>)this.redisUtil.getAllList("doctor_sign_file_log_list");
        this.redisUtil.deleteByKey("doctor_sign_file_log_list");
        String writeContent = sm3List.stream().collect(Collectors.joining("\n"));
        writeContent = writeContent + "\n";
        return writeContent;
    }

    private void writeToFile(String content) {
        String filePath = getFullSignLogPath();
        File file = new File(filePath);
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
        try(FileWriter fw = new FileWriter(filePath, true);
            BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(content);
        } catch (IOException e) {
            log.error("{}", e);
        }
    }

    private String getFullSignLogPath() {
        String filePath = this.systemConfig.getPath().getSignLogPath() + getSignLogFileName();
        return filePath;
    }

    private String getSignLogFileName() {
        String signLogFileName = redisUtil.getString("doctor_sign_file_log_current_name");
        return signLogFileName;
    }

    private void clearSignLogFileName() {
        this.redisUtil.deleteByKey("doctor_sign_file_log_current_name");
    }

    private String genSignLogFileName(Date date) {
        SimpleDateFormat sdfDir = new SimpleDateFormat("yyyy-MM-dd");
        String dateDir = sdfDir.format(date);
        this.currentFileRaw = 0;
        this.currentFileEndTime = null;
        this.currentFileStartTime = null;
        String currentFileName = dateDir + File.separator + UUID.randomUUID().toString().replace("-", "") + ".log";
        this.redisUtil.set("doctor_sign_file_log_current_name", currentFileName);
        log.info("currentFileName :[{}]", getSignLogFileName());
        return getSignLogFileName();
    }

    private String genSm3(TbSignFile signFile) {
        if (StringUtils.equals(signFile.getFileType(), "data")) {
            TbSignData signData = this.signDataDaoDao.findByFileCode(signFile.getFileCode());
            if (Objects.isNull(signData))
                return null;
            if (!StringUtils.equalsAny(signFile.getDataType(),  "p7", "p1" ))
                return null;
            String content = getContent(signFile, signData);
            return sm3(content);
        }
        String baseFilePath = this.systemConfig.getPath().getSignFilePath();
        String signFilePath = baseFilePath + (StringUtils.isNotBlank(signFile.getSignedFilePath()) ? signFile.getSignedFilePath() : signFile.getUnsignFilePath()) + ".pdf";
        String base64 = Base64Util.fileToBase64(signFilePath);
        return sm3(base64);
    }

    private boolean isWriteToFile(Date lastDate) {
        if (null != lastDate) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
            String lastDateFormat = sdf.format(lastDate);
            String currentDate = sdf.format(new Date());
            if (lastDateFormat.equals(currentDate))
                return false;
        }
        return true;
    }

    private String getContent(TbSignFile signFile, TbSignData signData) {
        if ("p7".equalsIgnoreCase(signFile.getDataType()))
            return signData.getSignedData();
        return String.format("%s@%s", signData.getUnsignData(), signData.getSignedData());
    }

    private Long getRedisListSize() {
        return this.redisUtil.getListSize("doctor_sign_file_log_list");
    }

    private List<TbSignFile> getSignFileList(Long queryId, int page) {
        long start = System.currentTimeMillis();
        QTbSignFile qTbSignFile = QTbSignFile.tbSignFile;
        BooleanExpression whereExp = qTbSignFile.id.gt(queryId);
        Date currentTime = new Date();
        Date pastTime = DateUtil.getPastDate(currentTime, this.systemConfig.getQuery().getDays());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
        String current = sdf.format(currentTime);
        String past = sdf.format(pastTime);
        if (!current.equals(past)) {
            log.info("past strategy:[{}], current strategy:[{}]", past, current);
            whereExp = whereExp.and(qTbSignFile.strategy.between(past, current));
        } else {
            log.info("current strategy:[{}]", current);
            whereExp = whereExp.and(qTbSignFile.strategy.eq(current));
        }
        PageRequest pageRequest = PageRequest.of(page, this.systemConfig.getSign().getPageSize());
        List<TbSignFile> signFileList = jpaQueryFactory.select(qTbSignFile).from(qTbSignFile).where(whereExp).offset(pageRequest.getOffset()).limit(pageRequest.getPageSize()).fetchResults().getResults();
        log.info("查询文件执行时间[{}]ms", System.currentTimeMillis() - start);
        return signFileList;
    }

    private String sm3(String data) {
        return Sm3Util.genSm3ByString(data);
    }

    public void test() {
        log.info("");
        try {
            Thread.sleep(7000L);
        } catch (InterruptedException e) {
            log.error("{}", e);
        }
        log.info("");
    }
}
